package org.xx.sipahi.controllers;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.xx.armory.commons.ForLogging;
import org.xx.armory.spring5.data.PageLinkCreator;
import org.xx.armory.spring5.jpa.PredicateBuilder;
import org.xx.armory.spring5.mvc.OutputTaskLog;
import org.xx.armory.spring5.mvc.ResourceNotFoundException;
import org.xx.sipahi.Category;
import org.xx.sipahi.jpa.CategoryPO;
import org.xx.sipahi.jpa.CategoryPO_;
import org.xx.sipahi.jpa.CategoryRepository;

import java.security.Principal;
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static java.util.Arrays.asList;
import static org.xx.armory.spring5.mvc.ModelUtils.JSON_VIEW;
import static org.xx.armory.spring5.mvc.ModelUtils.paramGreaterThan;
import static org.xx.armory.spring5.mvc.ModelUtils.paramNonNegative;
import static org.xx.armory.spring5.mvc.ModelUtils.paramNotBlank;
import static org.xx.armory.spring5.mvc.ModelUtils.parsePageArgs;
import static org.xx.sipahi.Category.ANY_AUTHORITY;

@Controller
@RequestMapping("/category")
public class CategoryController {
    @ForLogging
    private final Logger logger = LoggerFactory.getLogger(CategoryController.class);

    @Autowired
    private CategoryRepository categoryRepository;


    @GetMapping(value = {"/index", "/index({args})"})
    @Transactional(readOnly = true)
    public ModelAndView index(
            @PathVariable(required = false) String args,
            @RequestParam(defaultValue = "") String page
    ) {
        final var form = new CategorySearchForm(args);
        final var pageArgs = parsePageArgs(page);

        final var result = new ModelAndView();

        result.addObject("form", form);
        final var categoryPage = this.categoryRepository.findAll(
                (root, cq, cb) -> new PredicateBuilder()
                        .add(cb.equal(root.get(CategoryPO_.module), form.getModule()))
                        .add(() -> !form.getTitle().isBlank(), cb.like(root.get(CategoryPO_.title), "%" + form.getTitle() + "%"))
                        .build(cb), pageArgs.asc(CategoryPO_.id.getName()));

        result.addObject("categories", categoryPage.map(CategoryRepository::toCategory).toList());
        result.addObject("categoryCount", categoryPage.getTotalElements());
        result.addObject("pageLinkCreator", new PageLinkCreator());
        result.addObject("page", pageArgs);
        result.setViewName("/category/index");

        return result;
    }

    @GetMapping("/create")
    @Transactional(readOnly = true)
    public ModelAndView setUpCreate() {
        final var result = new ModelAndView();

        result.addObject("category", new Category(null, "", "", 10, ANY_AUTHORITY, 0, true, "", ""));

        return result;
    }

    @PostMapping("/create")
    @OutputTaskLog(category = "CATEGORY",
                   action = "CREATE_CATEGORY")
    @Transactional
    public ModelAndView create(
            @RequestParam String title,
            @RequestParam String description,
            @RequestParam int pageSize,
            @RequestParam String authority,
            @RequestParam int module,
            @RequestParam(defaultValue = "false") Boolean enabled,
            @RequestParam(value = "tpl1") String tpl1,
            @RequestParam(value = "tpl2") String tpl2,
            Principal principal
    ) {
        paramNotBlank(title, "title");
        paramGreaterThan(pageSize, 0, "pageSize");

        final var categoryPO = new CategoryPO();
        categoryPO.setTitle(title);
        categoryPO.setDescription(description);
        categoryPO.setPageSize(pageSize);
        categoryPO.setAuthority(authority);
        categoryPO.setModule(module);
        categoryPO.setEnabled(enabled);
        categoryPO.setTpl1(tpl1);
        categoryPO.setTpl2(tpl2);

        this.categoryRepository.saveAndFlush(categoryPO);

        return new ModelAndView("redirect:/category/index");
    }

    @GetMapping("/{id:^\\d+$}")
    @Transactional(readOnly = true)
    public ModelAndView setUpEdit(
            @PathVariable int id
    ) {
        paramNonNegative(id, "id");

        final var result = new ModelAndView("/category/edit");

        final var categoryPO = this.categoryRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(id));
        result.addObject("category", CategoryRepository.toCategory(categoryPO));

        return result;
    }

    @PostMapping("/{id:^\\d+$}")
    @OutputTaskLog(category = "CATEGORY",
                   action = "UPDATE_CATEGORY")
    @Transactional
    public ModelAndView edit(
            @PathVariable int id,
            @RequestParam String title,
            @RequestParam String description,
            @RequestParam int pageSize,
            @RequestParam String authority,
            @RequestParam int module,
            @RequestParam(defaultValue = "false") Boolean enabled,
            @RequestParam(value = "tpl1") String tpl1,
            @RequestParam(value = "tpl2") String tpl2,
            Principal principal
    ) {
        paramNotBlank(title, "title");
        paramGreaterThan(pageSize, 0, "pageSize");

        final var categoryPO = this.categoryRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(id));
        categoryPO.setTitle(title);
        categoryPO.setDescription(description);
        categoryPO.setPageSize(pageSize);
        categoryPO.setAuthority(authority);
        categoryPO.setModule(module);
        categoryPO.setEnabled(enabled);
        categoryPO.setTpl1(tpl1);
        categoryPO.setTpl2(tpl2);

        this.categoryRepository.saveAndFlush(categoryPO);

        return new ModelAndView("redirect:/category/index");
    }

    private IntStream idStream(
            String idArray
    ) {
        if (idArray == null || idArray.isEmpty()) {
            return IntStream.empty();
        } else {
            final var idArray_ = asList(idArray.split(","));
            Collections.reverse(idArray_);
            return idArray_.stream()
                           .filter(StringUtils::isNotBlank)
                           .mapToInt(NumberUtils::toInt)
                           .filter(id -> id > 0);
        }
    }

    @DeleteMapping("/{idArray:^[0-9,]+$}")
    @Transactional
    public ModelAndView delete(
            @PathVariable String idArray
    ) {
        paramNotBlank(idArray, "idArray");

        final var result = new ModelAndView(JSON_VIEW);

        result.addObject("result", idStream(idArray).mapToObj(id -> {
            final var po = this.categoryRepository.getOne(id);
            this.categoryRepository.delete(po);
            logger.trace("remove {}", po.getId());
            return new Category(id);
        }).collect(Collectors.toList()));

        return result;
    }
}
